package com.ease.component.controls.tree {


   import flash.display.DisplayObject;
   import flash.events.Event;

   import mx.collections.IViewCursor;
   import mx.controls.Tree;
   import mx.controls.treeClasses.ITreeDataDescriptor;
   import mx.core.ClassFactory;
   import mx.core.EventPriority;
   import mx.core.mx_internal;
   import mx.events.CollectionEvent;
   import mx.events.CollectionEventKind;
   import mx.events.PropertyChangeEvent;

   use namespace mx_internal;

   public class TriStateCheckBoxTree extends Tree {

      public function TriStateCheckBoxTree() {
         super();

         this.itemRenderer = new ClassFactory(CheckboxTreeItemRenderer);
         this.dataDescriptor = new CheckboxTreeNode();
      }

      //---------------------------------------------------------------------------
      // Instance Properties

      //-------------------------------------------------------
      // stateField

      private var _stateField:String = "state";

      /**
       * The name of the field in the data provider items to determine the state of
       * <code>TriStateCheckBox</code> in this renderer.
       *
       * @default "state"
       *
       * @see com.vmware.widgets.controls.TriStateCheckBox
       */
      public function get stateField():String {
         return _stateField;
      }

      /** @private */
      public function set stateField(value:String):void {
         if (_stateField == value) {
            return;
         }

         _stateField = value;
         invalidateDisplayList();
      }

      //-------------------------------------------------------
      // readOnly

      private var _readOnly:Boolean;

      /**
       * Represents the read only state of this tri state check box tree. If set to
       * <code>true</code>, makes all the nodes unclickable while user can still
       * expand/collapse the tree.
       *
       * @see com.vmware.widgets.controls.TriStateCheckBox
       */
      public function get readOnly():Boolean {
         return _readOnly;
      }

      /** @private */
      public function set readOnly(value:Boolean):void {
         if (_readOnly == value) {
            return;
         }
         _readOnly = value;
         invalidateDisplayList();
      }

      //---------------------------------------------------------------------------
      // Overridden Methods

      /**
       * @inheritDoc
       */
      override protected function updateDisplayList(unscaledWidth:Number,
                                                    unscaledHeight:Number):void {
         super.updateDisplayList(unscaledWidth, unscaledHeight);

         if (this.listItems.length > 0) {
            var length:int = this.listItems.length;

            for (var i:int = 0; i < length; i++) {
               var itemArray:Array = this.listItems[i];

               if (itemArray.length > 0) {

                  // Instance of renderer
                  var rendererInstance:DisplayObject = itemArray[0];
                  rendererInstance['stateField'] = _stateField;
                  rendererInstance['readOnly'] = _readOnly;
               }
            }
         }
      }

      //---------------------------------------------------------------------------
      // Instance Methods

      /**
       * Expands all the nodes of this tri state check box tree.
       *
       * If you set <code>dataProvider</code> and then immediately call
       * <code>expandAllNodes()</code> you may not see the correct behavior. You
       * should either wait for the component to validate or call the
       * <code>validateNow()</code> method.
       */
      public function expandAllNodes():void {
         expandCollapseAllNodes(true);
      }

      /**
       * Collapses all the nodes of this tri state check box tree.
       *
       * If you set <code>dataProvider</code> and then immediately call
       * <code>collapseAllNodes()</code> you may not see the correct behavior.
       * You should either wait for the component to validate  or call the
       * <code>validateNow()</code> method.
       */
      public function collapseAllNodes():void {
         expandCollapseAllNodes(false);
      }

      /**
       * Helper function to Expand/Collapse all the node of this state check box tree.
       */
      private function expandCollapseAllNodes(expand:Boolean):void{
         if(_rootModel == null){
            return;
         }

         var viewCursor:IViewCursor = _rootModel.createCursor();

         while (!viewCursor.afterLast) {
            var rootItem:* = viewCursor.current;
            var isBranch:Boolean = this.dataDescriptor.isBranch(rootItem, _rootModel);
            var hasChildren:Boolean = this.dataDescriptor.hasChildren(rootItem, _rootModel);

            if (rootItem != null && isBranch && hasChildren) {
               this.expandChildrenOf(rootItem, expand);
            }
            viewCursor.moveNext();
         }
      }

      /**
       * Recursively updates the state of the children of the current node.
       *
       * @param nodeData
       *    The data for the current node.
       *
       * @param state
       *    State to apply to the current node.
       */
      private function updateChildren(nodeData:Object, state:String):void {
         if (nodeData == null) {
            return;
         }

         var treeData:ITreeDataDescriptor = this.dataDescriptor;
         updateItemState(nodeData, state);

         if (!treeData.isBranch(nodeData)) {
            return;
         }

         var viewCursor:IViewCursor = treeData.getChildren(nodeData).createCursor();

         while (!viewCursor.afterLast) {
            this.updateChildren(viewCursor.current, state);
            viewCursor.moveNext();
         }
      }

      /**
       * Recursively updates the state of parents of the current node.
       *
       * @param nodeData
       *    The data for the current node.
       *
       * @param state
       *    State to apply to the current node.
       */
      private function updateParents(nodeData:Object, state:String):void {
         if (nodeData == null) {
            return;
         }

         updateItemState(nodeData, state);
         var parentNodeData:Object = this.getParentItem(nodeData);

         if (parentNodeData == null) {
            return;
         }

         var nodeState:String = this.getNodeState(parentNodeData);
         this.updateParents(parentNodeData, nodeState);
      }

      /**
       * Recursively updates the state of all the nodes in the tree data.
       */
      private function updateAllNodeState():void {
         if(_rootModel ==null){
            return;
         }

         var viewCursor:IViewCursor = _rootModel.createCursor();

         while (!viewCursor.afterLast) {
            var rootItem:* = viewCursor.current;
            var isBranch:Boolean = this.dataDescriptor.isBranch(rootItem, _rootModel);
            var hasChildren:Boolean = this.dataDescriptor.hasChildren(rootItem, _rootModel);

            if (rootItem != null && isBranch && hasChildren) {
               findAndUpdateLastLevel(rootItem);
            }
            viewCursor.moveNext();
         }
      }

      /**
       * Finds the last level in tree data and update its parents.
       *
       * @param nodeData
       *    The data for the current node.
       */
      private function findAndUpdateLastLevel(nodeData:Object):void {
         var isLastLevel:Boolean = true;
         var lastChild:Object;
         var treeData:ITreeDataDescriptor = this.dataDescriptor;
         var viewCursor:IViewCursor = treeData.getChildren(nodeData).createCursor();

         while (!viewCursor.afterLast) {
            var isBranch:Boolean = this.dataDescriptor.isBranch(viewCursor.current, _rootModel);
            var hasChildren:Boolean = this.dataDescriptor.hasChildren(viewCursor.current, _rootModel);

            if (isBranch && hasChildren) {
               isLastLevel = false;
               findAndUpdateLastLevel(viewCursor.current);
            }
            lastChild = viewCursor.current;
            viewCursor.moveNext();
         }

         if (isLastLevel) {
            var childState:String = itemToState(lastChild);
            updateParents(lastChild, childState);
         }
      }

      /**
       * Returns the state property value in the node based on the
       * <code>stateField</code> property.
       *
       * @param nodeData
       *    The data for the current node.
       *
       * @return
       *    The state of the node according to the states of its children.
       */
      private function getNodeState(nodeData:Object):String {
         var treeData:ITreeDataDescriptor = this.dataDescriptor;
         var viewCursor:IViewCursor = treeData.getChildren(nodeData).createCursor();
         var childData:Object = viewCursor.current;
         var nodeState:String = itemToState(childData);

         while (viewCursor.moveNext() && nodeState != TriStateCheckBox.STATE_TRANSIENT) {
            childData = viewCursor.current;

            if (itemToState(childData) != nodeState) {
               nodeState = TriStateCheckBox.STATE_TRANSIENT;
            }
         }

         return nodeState;
      }

      /**
       * Updates the state field in the node, the field is determined by the
       * <code>stateField</code> property.
       */
      private function updateItemState(item:Object, state:String):void {
         if (item == null) {
            return;
         }

         if (item is XML) {
            if (item[_stateField].length() != 0) {
               item[_stateField] = state;
            }
         } else if (item is Object) {
            if (item[_stateField] != null) {
               item[_stateField] = state;
            }
         }
      }

      /**
       * Returns the state based on <code>stateField<code> property.
       * If the function cannot find or convert the parameter to state string, it
       * will return TriStateCheckBox.STATE_UNCHECKED state.
       */
      private function itemToState(item:Object):String {
         var state:String = TriStateCheckBox.STATE_UNCHECKED

         if (item == null) {
            return state;
         }

         if (item is XML) {
            if (item[_stateField].length() != 0) {
               state = item[_stateField];
            }
         } else if (item is Object) {
            if (item[_stateField] != null) {
               state = item[_stateField];
            }
         }

         return state;
      }

      //---------------------------------------------------------------------------
      // Event Handlers

      //-------------------------------------------------------
      // collectionChangeHandler

      /**
       * @inheritDoc
       */
      override protected function collectionChangeHandler(event:Event):void {

         // Temporaily remove CollectionEvent event listender in order to avoid the
         // call backs to this handler function.
         wrappedCollection.removeEventListener(CollectionEvent.COLLECTION_CHANGE,
            collectionChangeHandler);

         // Update the data provider of this tri state checkbox tree to set appropriate
         // states of the child and parent nodes of this node.
         var items:Array = CollectionEvent(event).items;
         var itemLength:int = items.length;

         for (var i:int = 0; i < itemLength; i++) {
            var item:* = items[i];
            var nodeDate:Object;

            if (item is PropertyChangeEvent && item != null) {
               nodeDate = item.source as Object;
            } else if (item is Object) {
               nodeDate = item;
            }

            var eventKind:String =  CollectionEvent(event).kind;

            if (eventKind == CollectionEventKind.ADD ||
               eventKind == CollectionEventKind.REMOVE ||
               eventKind == CollectionEventKind.REPLACE ||
               eventKind == CollectionEventKind.MOVE) {
               updateAllNodeState();
            } else {
               var state:String = itemToState(nodeDate);

               if (state != TriStateCheckBox.STATE_TRANSIENT) {
                  updateChildren(nodeDate, state);
                  updateParents(nodeDate, state);
               }
            }
         }

         // Restore the CollectionEvent event handler.
         wrappedCollection.addEventListener(CollectionEvent.COLLECTION_CHANGE,
            collectionChangeHandler, false, EventPriority.DEFAULT_HANDLER, true);

         super.collectionChangeHandler(event);
      }
   }
}